反射效率讨论
使用反射来创建一个类的实例的效率,肯定是要比直接使用 new
低的,那么到底低多少呢?下面我们来测 试一下。我们使用 JMH(Java Microbenchmark Harness) 来进行基准测试,然后对比测试结果。
效率对比
实例创建效率对比
测试代码如下:
@Fork(1)
@BenchmarkMode(Mode.Throughput)
public class ReflectionTest01 {
@Benchmark
public void createDirect(Blackhole bh) {
User user = new User();
bh.consume(user);
}
@Benchmark
public void createReflection(Blackhole bh) throws Exception {
Class<?> clazz = Class.forName("tech.devguide.reflection.classload.model.User");
Constructor<User> constructor = (Constructor<User>) clazz.getConstructor();
Object obj = constructor.newInstance();
bh.consume(obj);
}
}
测试结果:
Benchmark Mode Cnt Score Error Units
ReflectionTest01.createDirect thrpt 5 432226002.753 ± 62148903.219 ops/s
ReflectionTest01.createReflection thrpt 5 2367241.614 ± 22070.486 ops/s
下面这段代码
Class<?> clazz = Class.forName("tech.devguide.reflection.classload.model.User");
Constructor<User> constructor = (Constructor<User>) clazz.getConstructor();
是为了获取类的构造函数,然后最终通过构造函数来创建对象。为了保证测试的准确性,我们仅考虑对象创建的差异。因此将此部分提取到方法外。然后再进行测试。完整代码如下:
@State(Scope.Benchmark)
@Fork(1)
@BenchmarkMode(Mode.Throughput)
public class ReflectionTest02 {
private Constructor<User> constructor;
public ReflectionTest02() {
try {
Class<?> clazz = Class.forName("tech.devguide.reflection.classload.model.User");
constructor = (Constructor<User>) clazz.getConstructor();
} catch (Exception e) {
e.printStackTrace();
}
}
@Benchmark
public void createDirect(Blackhole bh) {
User user = new User();
bh.consume(user);
}
@Benchmark
public void createReflection(Blackhole bh) throws Exception {
Object obj = constructor.newInstance();
bh.consume(obj);
}
}
测试结果:
Benchmark Mode Cnt Score Error Units
ReflectionTest02.createDirect thrpt 5 437689219.856 ± 52931283.013 ops/s
ReflectionTest02.createReflection thrpt 5 170329396.922 ± 5503310.701 ops/s
可以看出有了极大的提升,但是依然没有直接创建对象快。我认为这个才是反射的效率与直接调用的效率对比。性能有2~3倍的差异,但是差异并不是特别大。
方法调用效率对比
@Fork(1)
@BenchmarkMode(Mode.Throughput)
public class ReflectionTest3 {
@Benchmark
public void testDirect(Blackhole bh) {
User user = new User();
user.setName("张三");
bh.consume(user.getName());
}
@Benchmark
public void testReflection(Blackhole bh) throws Exception {
User user = new User();
Field field = User.class.getDeclaredField("name");
field.setAccessible(true);
field.set(user, "张三");
bh.consume(field.get(user));
}
}
测试结果:
Benchmark Mode Cnt Score Error Units
ReflectionTest3.testDirect thrpt 5 2569734655.736 ± 230142819.512 ops/s
ReflectionTest3.testReflection thrpt 5 47565197.953 ± 4258351.454 ops/s
现在将字段获取放到方法外,再进行测试
@State(Scope.Benchmark)
@Fork(1)
@BenchmarkMode(Mode.Throughput)
public class ReflectionTest4 {
private final Field field;
public ReflectionTest4() {
try {
field = User.class.getDeclaredField("name");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
@Benchmark
public void testDirect(Blackhole bh) {
User user = new User();
user.setName("张三");
bh.consume(user.getName());
}
@Benchmark
public void testReflection(Blackhole bh) throws Exception {
User user = new User();
field.setAccessible(true);
field.set(user, "张三");
bh.consume(field.get(user));
}
}
测试结果:
Benchmark Mode Cnt Score Error Units
ReflectionTest4.testDirect thrpt 5 2576261981.192 ± 307117964.204 ops/s
ReflectionTest4.testReflection thrpt 5 82960500.304 ± 3457384.884 ops/s
可以看到虽然有了很明显的提升,但是差距依然很大。
为什么反射的效率慢
在上面的代码中对象创建有 2~3 倍的差异,而方法的调用有30多倍的差异。
官方说明:
Performance Overhead
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能比非反射操作的性能慢,在性能敏感型应用程序中经常调用的代码段中应避免使用反射操作。
除了上面官方提到的一个原因外还有其他的原因。
- 在获取类、构造函数、字段、方法等时,会进行额外的安全检查。
- 所有操作都需要先进行查找或发现。如根据类的完全限定名获取类,根据方法名 获取方法等。
- 参数需要通过装箱/拆箱操作,参数会被打包成数组。
- 异常信息回包裹在
InvocationTargetException
中重新抛出。 - JIT 编译器执行的最强大的转换之一是自动方法内联。不幸的是,由于反射性调用的动态性质,它们在一般情况下通常不会内联。
强制类型转换的性能开销可以忽略不计。有的文章中说反射中的强制类型转换会导致额外的性能开销,这种说法是不正确的。以下是一个强制类型转换和没有强制类型转换的性能测试结果:
Benchmark Mode Cnt Score Error Units
CastTest.test1 thrpt 5 423612986.204 ± 45613060.042 ops/s
CastTest.test2 thrpt 5 428994015.602 ± 29943413.860 ops/s
上面是没有强制类型转换的结果,下面是有强制类型转换的结果。从结果中可以看出来基本没有差异。
此外并非所有反射方法都无法内联,有一些例外情况是可以被内联优化的。
The reflection implementation generates Java bytecode (using
MethodAccessorGenerator.generateMethod()
) for the call. This can make it easier for the JIT compiler to inline the reflective call if certain conditions hold, such as the Method object being rooted in a static final field and the target method being static (or having a definitely known receiver type).
也即如果反射的方法在一个 static final
字段上,并且这个方法是一个静态方法。是可能会被JIT编译器进行内联优化的。
总结
反射的功能很强大,但是性能慢,如果能避免使用它,最好避免使用。但也不能一棒子打死 “就是不能用反射”,反射在很多场景下还是非常实用的,特别是编写一些共用组件时。在 Spring 和 MyBatis 等一些开源框架中都大量的使用了反射。
反射还有一个问题,就是会暴露内部的字段或方法(如一些 private
字段或方法 ),使其可以突破原有访问限制而被访问,可能会造成一些麻烦。